
//import { mat4 } from "../matrix/index";
import { WebGLUtil } from "../utils/WebGLUtil";
import { Render } from "./Render";

/*
 * @Author: Snow
 * @Date: 2021-08-30 18:35:13
 * @Description: 
 */

export interface IBufferInfo{
    vPos:WebGLBuffer,
    vColor:WebGLBuffer,
    vNormal?:WebGLBuffer,
    indices?:WebGLBuffer
}

export interface IAttribute{
    aVertexPosition?:number,
    aVertexColor?:number,
    aNomal?:number,
}

export interface IUniform{
    uColor?:WebGLUniformLocation,
    uModelViewMatrix?:WebGLUniformLocation,
    uProjectionMatrix?:WebGLUniformLocation,
    uViewMatrix?:WebGLUniformLocation,
}

export interface IProgramInfo{
    attributes:IAttribute,
    uniforms:IUniform,
}

export class WebglBase{
    
    public vsSource:string;

    public fsSource:string;

    public shaderProgram:WebGLProgram;

    public programInfo:IProgramInfo;

    public bufferInfo:IBufferInfo;

    protected rotation:number = 0; ///旋转

    public projectionMatrix:Float32Array;

    public modelViewMatrix:Float32Array;

    public viewMatrix:Float32Array;

    protected ts:number = 0;

    public gl:WebGLRenderingContext;

    public uiParam = {};

    //public 

    public constructor(){
        this.gl = Render.GL;
        this.initUITools();
        this.initGLSL();
        this.initShaderProgram();
        this.initProgramInfo();
        this.initBuffer();
    }

    public initUITools():void{
        const gl = Render.GL;
        let translation = [200, 150];
        let angleInRadians = 0;
        let scale = [1, 1];

        // Setup a ui.
        const webglLessonsUI = (window as any).webglLessonsUI;
        webglLessonsUI.setupSlider("#x", {value: translation[0], slide: this.onUpateSlide(this,"x"), max: gl.canvas.width });
        webglLessonsUI.setupSlider("#y", {value: translation[1], slide: this.onUpateSlide(this,"y"), max: gl.canvas.height});
        webglLessonsUI.setupSlider("#z", {value: 0, slide: this.onUpateSlide(this,"z"), max: 5, min:-5,step:0.01,precision:2});
        webglLessonsUI.setupSlider("#angle", {slide: this.onUpateSlide(this,"angle"), max: 360});
        webglLessonsUI.setupSlider("#scaleX", {value: scale[0], slide: this.onUpateSlide(this,"scaleX"), min: -5, max: 5, step: 0.01, precision: 2});
        webglLessonsUI.setupSlider("#scaleY", {value: scale[1], slide: this.onUpateSlide(this,"scaleY"), min: -5, max: 5, step: 0.01, precision: 2});
        this.createSlider("scaleZ",{value: scale[1], slide: this.onUpateSlide(this,"scaleZ"), min: -5, max: 5, step: 0.01, precision: 2})
    }

    protected createSlider(id:string,options:any):void{
        const webglLessonsUI = (window as any).webglLessonsUI;
        let div:Element = document.querySelector("#ui");
        let node = document.createElement("div")
        node.id = id
        div.appendChild(node);
        webglLessonsUI.setupSlider("#" + id, options);
    }
    

    protected onUpateSlide(thisObj:WebglBase,id:string):(evt:any,ui:any)=>any{
        return (evt:any,ui:any):any=>{
            thisObj.uiParam[id] = ui.value;
            //console.log(thisObj.uiParam);
        }
    }

    protected createTabel(name:string):void{
        let ui:Element = document.querySelector("#ui");
        let table = document.querySelector("#table");
        let tbody = document.querySelector("#tbody");
        if(!table){
            table = document.createElement("table");
            tbody = document.createElement("tbody");
            table.appendChild(tbody);
            ui.appendChild(table);
            ($(table) as any).tableDnD({onDrop: (obj:any)=>{
                console.error("111" + obj);
            }});
        }

        let tr = document.createElement("tr");
        let td = document.createElement("td");
        let chk = document.createElement("input");
        chk.value = name;
        chk.type = "checkbox";
        chk.id = name;
        chk.checked = false;
        chk.onchange = this.onTableChange(this,name);
        td.appendChild(chk);
        td.appendChild(document.createTextNode('≡ ' + name));
        tr.appendChild(td);
        tbody.appendChild(tr);
    }

    protected onTableChange(thisObj:WebglBase,name:string):(ui:Event)=>any{
        return (ui:Event):any=>{
            //console.log("onTableChange",ui);
            let checkbox = document.querySelector("#"+name) as HTMLInputElement;
            thisObj.uiParam[name] = checkbox.checked;
            console.log(thisObj.uiParam);
        }
    }

    public initGLSL():void{
        this.vsSource = `
            attribute vec4 aVertexPosition;
            attribute vec4 aVertexColor;

            uniform mat4 uModelViewMatrix;
            uniform mat4 uProjectionMatrix;
            uniform mat4 uViewMatrix;

            varying lowp vec4 vColor;

            void main() {
                gl_Position = uProjectionMatrix * uViewMatrix * uModelViewMatrix * aVertexPosition;
                vColor = aVertexColor;
            }
        `;
        
        this.fsSource = `
            varying lowp vec4 vColor;
            void main() {
                gl_FragColor = vColor;
            }
        `;
    }


    public initShaderProgram():WebGLProgram{
        const gl = Render.GL;
        // this.shaderProgram = gl.createProgram();
        
        // const vertexShardr = this.loadShader(gl.VERTEX_SHADER,this.vsSource);
        // const fragmentShardr = this.loadShader(gl.FRAGMENT_SHADER,this.fsSource);

        // gl.attachShader(this.shaderProgram, vertexShardr);
        // gl.attachShader(this.shaderProgram, fragmentShardr);
        // gl.linkProgram(this.shaderProgram);
        
        // if(!gl.getProgramParameter(this.shaderProgram,gl.LINK_STATUS)){
        //     alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(this.shaderProgram));
    	// 	return null;
        // }

        // // NOTE! These are only here to unclutter the diagram.
        // // It is safe to detach and delete shaders once
        // // a program is linked though it is arguably not common.
        // // and I usually don't do it.
        // gl.detachShader(this.shaderProgram, vertexShardr)
        // gl.deleteShader(vertexShardr)
        // gl.detachShader(this.shaderProgram, fragmentShardr)
        // gl.deleteShader(fragmentShardr)

        this.shaderProgram = WebGLUtil.createProgramFromScripts(gl,this.vsSource,this.fsSource);

        return this.shaderProgram;
    }

    private loadShader(type:GLenum,source:string):WebGLShader{
        const gl = Render.GL;
        const shader =  gl.createShader(type);
        gl.shaderSource(shader,source);
        gl.compileShader(shader);

        if(!gl.getShaderParameter(shader,gl.COMPILE_STATUS)){
            alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
			gl.deleteShader(shader);
			return null;
        }

        return shader;
    }

    public initProgramInfo():void{
        const gl = Render.GL;
        let attributes:IAttribute = {};
        let uniforms:IUniform = {};
        let programInfo:IProgramInfo = {attributes,uniforms};

        this.programInfo = programInfo;
        attributes.aVertexPosition = gl.getAttribLocation(this.shaderProgram,"aVertexPosition");
        attributes.aVertexColor = gl.getAttribLocation(this.shaderProgram,"aVertexColor");
        uniforms.uProjectionMatrix = gl.getUniformLocation(this.shaderProgram,"uProjectionMatrix");
        uniforms.uModelViewMatrix = gl.getUniformLocation(this.shaderProgram,"uModelViewMatrix");
        uniforms.uViewMatrix = gl.getUniformLocation(this.shaderProgram,"uViewMatrix");
    }

    public initBuffer():IBufferInfo{
        const gl = Render.GL;
        const vPos = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER,vPos);

        let vertices = [
			// Front face
			-1.0, -1.0,  1.0,
			1.0, -1.0,  1.0,
			1.0,  1.0,  1.0,
			-1.0,  1.0,  1.0,

			// Back face
			-1.0, -1.0, -1.0,
			-1.0,  1.0, -1.0,
			1.0,  1.0, -1.0,
			1.0, -1.0, -1.0,

			// Top face
			-1.0,  1.0, -1.0,
			-1.0,  1.0,  1.0,
			1.0,  1.0,  1.0,
			1.0,  1.0, -1.0,

			// Bottom face
			-1.0, -1.0, -1.0,
			1.0, -1.0, -1.0,
			1.0, -1.0,  1.0,
			-1.0, -1.0,  1.0,

			// Right face
			1.0, -1.0, -1.0,
			1.0,  1.0, -1.0,
			1.0,  1.0,  1.0,
			1.0, -1.0,  1.0,

			// Left face
			-1.0, -1.0, -1.0,
			-1.0, -1.0,  1.0,
			-1.0,  1.0,  1.0,
			-1.0,  1.0, -1.0
		];

        gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(vertices),gl.STATIC_DRAW);

        const colorBuffer = gl.createBuffer();
		const colors = [
			[1.0,  1.0,  1.0,  1.0],    // Front face: white
			[1.0,  0.0,  0.0,  1.0],    // Back face: red
			[0.0,  1.0,  0.0,  1.0],    // Top face: green
			[0.0,  0.0,  1.0,  1.0],    // Bottom face: blue
			[1.0,  1.0,  0.0,  1.0],    // Right face: yellow
			[1.0,  0.0,  1.0,  1.0]     // Left face: purple
		];

		let generatedColors = [];

		for (let j=0; j<6; j++) {
			var c = colors[j];
			for (let i=0; i<4; i++) {
				generatedColors = generatedColors.concat(c);
			}
		}

		gl.bindBuffer(gl.ARRAY_BUFFER,colorBuffer);
		gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(generatedColors),gl.STATIC_DRAW);
		
		const cubeVerticesIndexBuffer = gl.createBuffer();
		gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVerticesIndexBuffer);

		// This array defines each face as two triangles, using the
		// indices into the vertex array to specify each triangle's
		// position.

		var cubeVertexIndices = [
		0,  1,  2,      0,  2,  3,    // front
		4,  5,  6,      4,  6,  7,    // back
		8,  9,  10,     8,  10, 11,   // top
		12, 13, 14,     12, 14, 15,   // bottom
		16, 17, 18,     16, 18, 19,   // right
		20, 21, 22,     20, 22, 23    // left
		];

        // Now send the element array to GL

        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW);

        this.bufferInfo = {vPos:vPos,vColor:colorBuffer,indices:cubeVerticesIndexBuffer};

        return this.bufferInfo;
    }

    /**
     * @Author: Snow
     * @description: 更新矩阵
     * @param {*}
     * @return {*}
     */    
    public updateMatrix():void{
        const gl = Render.GL;
        const mat4 = (window as any).glMatrix.mat4;
        const fieldOfView = 45 * Math.PI / 180;   // in radians
		const aspect = Render.Canvas.clientWidth / Render.Canvas.clientHeight;
		const zNear = 0.1;
		const zFar = 100.0;
		const projectionMatrix = mat4.create();
        const viewMatrix = mat4.create();
		
		mat4.perspective(projectionMatrix,
                   fieldOfView,
                   aspect,
                   zNear,
                   zFar);
        
        const modelViewMatrix = mat4.create();
        //mat4.scale(modelViewMatrix,modelViewMatrix,[0.1,0.1,0.1]);
        mat4.translate(modelViewMatrix,     // destination matrix
            modelViewMatrix,     // matrix to translate
            [-0.0, 0.0, -6.0]);  // amount to translate

        mat4.rotate(modelViewMatrix,  // destination matrix
            modelViewMatrix,  // matrix to rotate
            this.rotation,   // amount to rotate in radians
            [0, 1, 1] // axis to rotate around
        );
        
        this.projectionMatrix = projectionMatrix;
        this.modelViewMatrix = modelViewMatrix;
        this.viewMatrix = viewMatrix;
    }


    /**
     * @Author: Snow
     * @description: 渲染
     * @param {number} ts 时间戳
     * @return {*}
     */    
    public render(ts?:number):void{
       // console.log(ts);
        this.rotation += ts - this.ts;
        this.ts = ts;
        this.draw();

    }

    public draw(param?:any):void{
        const gl = Render.GL;
        WebGLUtil.resizeCanvasToMatchDisplaySize(gl);
		gl.clearColor(0.0,0.0,0.0,1.0);
		gl.clearDepth(1.0);
		gl.enable(gl.DEPTH_TEST);
		gl.depthFunc(gl.LEQUAL);
		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        this.updateMatrix();
        //上传顶点数据到shader
        {
            const numComponents = 3;  // pull out 3 values per iteration
			const type = gl.FLOAT;    // the data in the buffer is 32bit floats
			const normalize = false;  // don't normalize
			const stride = 0;         // how many bytes to get from one set of values to the next
									// 0 = use type and numComponents above
			const offset = 0;         // how many bytes inside the buffer to start from
			gl.bindBuffer(gl.ARRAY_BUFFER, this.bufferInfo.vPos);
			gl.vertexAttribPointer(
				this.programInfo.attributes.aVertexPosition,
				numComponents,
				type,
				normalize,
				stride,
				offset);
			gl.enableVertexAttribArray(
				this.programInfo.attributes.aVertexPosition);
        }
        
        ////上传顶点颜色
        {
			gl.enableVertexAttribArray(this.programInfo.attributes.aVertexColor);	
			gl.bindBuffer(gl.ARRAY_BUFFER,this.bufferInfo.vColor);
			gl.vertexAttribPointer(
				this.programInfo.attributes.aVertexColor,
				4,
				gl.FLOAT,
				false,
				0,
                0);
        }

        // Tell WebGL which indices to use to index the vertices
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.bufferInfo.indices);

        // Tell WebGL to use our program when drawing\
        gl.useProgram(this.shaderProgram);

        gl.uniformMatrix4fv(
			this.programInfo.uniforms.uProjectionMatrix,
			false,
			this.projectionMatrix);
		gl.uniformMatrix4fv(
			this.programInfo.uniforms.uModelViewMatrix,
			false,
			this.modelViewMatrix);
        gl.uniformMatrix4fv(
                this.programInfo.uniforms.uViewMatrix,
                false,
                this.viewMatrix);
        
        gl.drawElements(gl.TRIANGLES,36,gl.UNSIGNED_SHORT,0);
    }
}
